package cn.pink.common.id;

import cn.pink.common.support.tools.rpc.RpcTool;
import cn.pink.core.Port;
import cn.pink.core.PortTask;
import cn.pink.core.config.DeployConfig;
import cn.pink.core.config.IronConfig;
import cn.pink.core.support.Log;

/**
 * ID分配池
 * 不同部署环境, 不同规则
 * 逻辑id最大值9999999999999, 13位.
 * 渠道id最大值99, 2位.(分布式, 集群部署不需要)
 * 服务器id最大值9999, 4位.(分布式, 集群部署不需要)
 *
 * 单机滚服环境: 19位
 * 分布式全球同服环境: 13位
 *
 * 假设一个服务器运营5年，这样计算一下每秒可分配的id数
 * 每秒可分配的逻辑id数 = 9999999999999 / (3600 * 24 * 365 * 5) = 63419
 *
 * @Author: pink
 * @Date: 2022/8/1 17:25
 */
public class IdAllotPool {
    /** 所属port */
    private Port port;

    /** 表示id的位数(十进制的位数) */
    public static final int ID_DECIMAL_NUM = 13;

    /** 每次申请的数量 */
    private static final int MAX_CACHE = 100000;
    /** 剩余警戒值，小于该值时就立即申请ID */
    private static final int LOW_WATER_MARK = 5000;

    /** 服务器id，有效值0-9999 */
    private long serverId;

    /** 当前id池 */
    private long currentIdBegin = 0;
    private long currentIdEnd = -1;

    private long backupIdBegin = 0;
    private long backupIdEnd = -1;

    /** 是否正在分配ID */
    private volatile boolean applying = false;

    public IdAllotPool(Port port) {
        this.port = port;

        if(IronConfig.COMMON_DEPLOY != DeployConfig.SIMPLE) {
            this.serverId = 9L * (long) Math.pow(10, ID_DECIMAL_NUM);
        }
        else {
            this.serverId = IronConfig.SERVER_INDEX * (long) Math.pow(10, ID_DECIMAL_NUM);
        }

        // 初始化id池
        port.addTask(new PortTask() {
            @Override
            public void execute(Port port) {
                // Port启动时，同步申请一次id
                apply();
                loadBackupPool();
            }
        });
    }

    /**
     * 申请id
     */
    private void apply() {
        applying = true;

        RpcTool.getInstance().idAllot(MAX_CACHE).onSuccess(range -> {
            // 设置申请状态
            applying = false;

            // 分配到的ID范围
            backupIdBegin = range.getKey();
            backupIdEnd = range.getValue();

            Log.common.info("ID apply success, port={}, begin={}, end={}", port.getNodeId() + "." + port.getId(), backupIdBegin, backupIdEnd);
        });
    }

    private void loadBackupPool() {
        currentIdBegin = backupIdBegin;
        currentIdEnd = backupIdEnd;

        backupIdBegin = 0;
        backupIdEnd = -1;
    }

    /**
     * 分配一个ID
     */
    public long applyId() {
        if (currentIdBegin > currentIdEnd) {
            loadBackupPool();
            if (currentIdBegin > currentIdEnd) {
                Log.error.error("ID allot failed, pool is empty, thread will block, port={}", port.getNodeId() + "." + port.getId(), new Throwable());

                //一般不会到这
                // 正常id池数量小于警戒值时就会自动申请id
                // 此处无id可分配也许是申请结果还没有返回
                // 这时没有别的办法，只能同步等待
                // 阻塞当前线程
                apply();

                //这里只能同步等待了, 模拟同步写法
                while (!applying) {
                    Thread.onSpinWait();
                }

                loadBackupPool();
            }

//            return applyId();
        }

        // 小于警戒值，申请新ID
        if ((currentIdEnd - currentIdBegin < LOW_WATER_MARK) && !applying) {
            apply();
        }

        long id = currentIdBegin++;

        return serverId + id;
    }
}
